package com.lifeonwalden.ms.caller.http;

import com.lifeonwalden.ms.caller.http.bean.MultipartParam;
import com.lifeonwalden.ms.caller.http.handler.ResultHandler;
import com.lifeonwalden.ms.common.logger.LoggerUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.mime.FileBody;
import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.entity.mime.StringBody;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.FormattedMessage;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Function;

public class HttpCaller {
    private static final Logger logger = LoggerUtil.getLogger(HttpCaller.class);

    static <T> T get(String url, List<Header> headers, List<NameValuePair> params, ResultHandler<T> handler) {
        LoggerUtil.debug(logger, "get", url, params);

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            URIBuilder uriBuilder = new URIBuilder(url);
            if (null != params) {
                uriBuilder.addParameters(params);
            }
            HttpGet httpGet = new HttpGet(uriBuilder.build());
            if (null != headers) {
                headers.forEach(header -> httpGet.addHeader(header));
            }
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                try (HttpEntity entity = response.getEntity()) {
                    return handler.process(entity);
                }
            }
        } catch (IOException | URISyntaxException e) {
            logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

            throw new RuntimeException(e);
        }
    }

    static String getString(String url, List<Header> headers, List<NameValuePair> params, Charset charset) {
        LoggerUtil.debug(logger, "getString", url, params, charset);

        return get(url, headers, params, entity -> {
            try {
                return EntityUtils.toString(entity, charset);
            } catch (IOException | ParseException e) {
                logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                throw new RuntimeException(e);
            }
        });
    }

    static String getString(String url, Charset charset) {
        LoggerUtil.debug(logger, "getString", url, charset);

        return get(url, null, null, entity -> {
            try {
                return EntityUtils.toString(entity, charset);
            } catch (IOException | ParseException e) {
                logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                throw new RuntimeException(e);
            }
        });
    }

    static String getString(String url, List<Header> headers, List<NameValuePair> params) {
        LoggerUtil.debug(logger, "getString", url, headers, params);

        return get(url, headers, params, entity -> {
            try {
                return EntityUtils.toString(entity);
            } catch (IOException | ParseException e) {
                logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                throw new RuntimeException(e);
            }
        });
    }

    static String getString(String url) {
        LoggerUtil.debug(logger, "getString", url);

        return get(url, null, null, entity -> {
            try {
                return EntityUtils.toString(entity);
            } catch (IOException | ParseException e) {
                logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                throw new RuntimeException(e);
            }
        });
    }

    static byte[] getByte(String url, List<Header> headers, List<NameValuePair> params) {
        LoggerUtil.debug(logger, "getByte", url, headers, params);

        return get(url, headers, params, entity -> {
            try {
                return EntityUtils.toByteArray(entity);
            } catch (IOException e) {
                logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                throw new RuntimeException(e);
            }
        });
    }

    static byte[] getByte(String url) {
        LoggerUtil.debug(logger, "getByte", url);

        return get(url, null, null, entity -> {
            try {
                return EntityUtils.toByteArray(entity);
            } catch (IOException e) {
                logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                throw new RuntimeException(e);
            }
        });
    }

    static <P> String postJson(String url, List<Header> headers, P params, Function<P, String> jsonConverter) {
        LoggerUtil.debug(logger, "post", url);
        return postJson(url, headers, params, jsonConverter, StandardCharsets.UTF_8, null);
    }

    static <P> String postJson(String url, List<Header> headers, P params, Function<P, String> jsonConverter, Charset encodeCharset, Charset decodeCharset) {
        String content = jsonConverter.apply(params);
        LoggerUtil.debug(logger, "post", url, content);

        return post(url, headers,
                new ByteArrayEntity(content.getBytes(encodeCharset), ContentType.APPLICATION_JSON.withCharset(encodeCharset)),
                entity -> {
                    try {
                        if (null != decodeCharset) {
                            return EntityUtils.toString(entity, decodeCharset);
                        } else {
                            return EntityUtils.toString(entity);
                        }
                    } catch (IOException | ParseException e) {
                        logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                        throw new RuntimeException(e);
                    }
                });
    }

    static <P> byte[] postJson(String url, List<Header> headers, P params, Function<P, String> jsonConverter, Charset encodeCharset) {
        String content = jsonConverter.apply(params);
        LoggerUtil.debug(logger, "post", url, content);

        return post(url, headers,
                new ByteArrayEntity(content.getBytes(encodeCharset), ContentType.APPLICATION_JSON.withCharset(encodeCharset)),
                entity -> {
                    try {
                        return EntityUtils.toByteArray(entity);
                    } catch (IOException e) {
                        logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                        throw new RuntimeException(e);
                    }
                });
    }

    static String postFile(String url, MultipartParam param, List<Header> headerList, Charset encodeCharset, Charset decodeCharset) {
        LoggerUtil.debug(logger, "post", url);

        MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.LEGACY).setCharset(encodeCharset);
        param.getFiles().forEach(fileParam -> entityBuilder.addPart(fileParam.getParamName(), new FileBody(fileParam.getFile(), ContentType.DEFAULT_BINARY,
                StringUtils.isEmpty(fileParam.getFileName()) ? fileParam.getFile().getName() : fileParam.getFileName())));
        param.getParams().forEach(nameValuePair -> entityBuilder.addPart(nameValuePair.getName(), new StringBody(nameValuePair.getValue(), ContentType.create("text/plain", encodeCharset))));

        return post(url, headerList, entityBuilder.build(), entity -> {
            try {
                if (null != decodeCharset) {
                    return EntityUtils.toString(entity, decodeCharset);
                } else {
                    return EntityUtils.toString(entity);
                }
            } catch (IOException | ParseException e) {
                logger.error(new FormattedMessage("Failed to process get request : {}", url), e);

                throw new RuntimeException(e);
            }
        });
    }

    static <T> T post(String url, List<Header> headers, HttpEntity params, ResultHandler<T> handler) {
        LoggerUtil.debug(logger, "post", url);

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(URI.create(url));
            httpPost.setEntity(params);
            if (null != headers) {
                headers.forEach(header -> httpPost.addHeader(header));
            }
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                try (HttpEntity entity = response.getEntity()) {
                    return handler.process(entity);
                }
            }
        } catch (IOException e) {
            logger.error(new FormattedMessage("Failed to process post request : {}", url), e);

            throw new RuntimeException(e);
        }
    }
}
